using System;
using System.Threading;
using System.Collections;
using XTCast.Mp3;

namespace XTCast.Radio {
	/// <summary>
	/// Manages the use of buffers between DJs and Transmitters
	/// </summary>
	public class BufferManager {

		private const int lowMarkBuffers = 10;
		private const int highMarkBuffers = 15;
		private const int trailingBuffers = 3;

		private const System.String kPropBufferCount = "buffer-count";

		Thread bufferThread;
		public BufferManager(Station station, BufferConfig c ){
			Buffer.Initialize(c);
			this.station = station;
			bufferThread = new Thread(new ThreadStart(BufferManagerThread));
			bufferThread.IsBackground=true;
			bufferThread.Priority = ThreadPriority.Lowest;
			bufferThread.Name="Buffer Manager Thread";
			bufferThread.Start();
		}

		public override string ToString() {
			return String.Format("BM{0,13:G}", Station.ID);
		}

		private void BufferManagerThread() {
			Thread.Sleep(1000);
			BufferIterator bi = this.GetBufferIterator();
			Throttle t = new Throttle();
			while(true) {
				try {
					playingBuffer = bi.NextBuffer();					
					if (playingBuffer!=null) {
						while (TrailingCount > trailingBuffers)
							if (station.Active)
								buffers.Remove(LastBuffer);
							else
								t.Add(TimeSpan.FromSeconds(1),true);
						t.Add(TimeSpan.FromMilliseconds(playingBuffer.LengthMillis),true);
						if (playingBuffer.Track != playingTrack) {
							playingTrack = playingBuffer.Track;
							Station.OnTrackChanged(playingTrack);
						}
					}
				}
				catch (ThreadAbortException ex) {
				}
				catch (Exception ex) {
					Log.Error(ex);
				}
			}
		}

		public void Add(Buffer buf) {
			lock(buffers.SyncRoot) {
				buffers.Insert(0,buf);
				Monitor.PulseAll(buffers.SyncRoot);
			}
		}

		public void Add(Buffer[] bufs) {
			lock(buffers.SyncRoot) {
				foreach (Buffer buf in bufs)
					if (buf!=null)
						buffers.Insert(0,buf);
				Monitor.PulseAll(buffers.SyncRoot);
			}
		}

		public bool Fill {
			get{
				if (fill && AheadCount>=highMarkBuffers)
					fill=false;
				else if (!fill && AheadCount<=lowMarkBuffers)
					fill=true;
				return fill;
			}
		}

		public int NumToFill() {
			return Math.Max(0,highMarkBuffers-AheadCount);
		}

		public int Count {
			get { return buffers.Count; }
		}

		public int TrailingCount {
			get {
				int playingBuffPos = buffers.IndexOf(playingBuffer);
				if (playingBuffPos<0)
					return -1;
				else
					return buffers.Count - 1 - playingBuffPos;
			}
		}

		public int AheadCount {
			get {
				int playingBuffPos = buffers.IndexOf(playingBuffer);
				if (playingBuffPos<0)
					return buffers.Count;
				else
					return playingBuffPos+1;
			}
		}

		public void FlushBuffers() 
		{
			FlushBuffers(TimeSpan.MaxValue);
		}

		public TimeSpan FlushBuffers(TimeSpan maxTime) {
			TimeSpan totalFlushed = TimeSpan.Zero;
			int idx, min, max;
			idx=0; min=int.MaxValue; max=int.MinValue;
			lock(buffers.SyncRoot) {
				foreach (Buffer buffer in buffers) {
					if (maxTime > TimeSpan.Zero) {
						min = Math.Min(min,idx);
						max = Math.Max(max,idx);
						buffer.Invalid=true;
						TimeSpan ts = TimeSpan.FromMilliseconds(buffer.LengthMillis);
						totalFlushed += ts;
						maxTime -= ts;
					}
					idx++;
				}
				if (min<int.MaxValue)
					buffers.RemoveRange(min,max-min+1);
				fill=true;
				return totalFlushed;
			}
		}

		public Buffer LoadBuffer(IFrameReader fin, Track track) {
			Buffer buf = new Buffer(this);
			Log.Verbose(this, "Loading buffer from " + track + "...");
			try {
				if (!buf.ReadFrom(fin, track))	// Will block for a while (file input)
					return null;
			} finally {
				Log.Verbose(this, "...done loading " + buf);
			}
			return buf;
		}

		public Buffer[] LoadBuffers(IFrameReader fin, Track track, int numBuffers) {
			Buffer[] bufs;
			Buffer buf = LoadBuffer(fin, track);
			if (buf==null) return null;
			bufs = new Buffer[numBuffers];
			bufs[0] = buf;
			for (int i=1;i<numBuffers;i++) {
				buf = LoadBuffer(fin, track);
				if (buf != null) 
					bufs[i] = buf;
				else
					return bufs;
			}
			return bufs;
		}
		public Station Station {
			get {
				return station;
			}
		}

		private Station station;
		private ArrayList buffers = ArrayList.Synchronized(new ArrayList());
		private Buffer playingBuffer;
		private Track playingTrack;
		private bool fill=true;				/// Am I accepting buffers right now
		
		public bool Ogg{
			get {
				return false;
			}
		}

		virtual public bool IsEmpty {
			get {
				return (buffers.Count<=0);
			}
		}

		virtual public Buffer FirstBuffer {		
			get {
				if (buffers.Count>0) 
					return (Buffer) buffers[0];
				else
					return null;
			}
		}

		public Buffer MiddleBuffer {		
			get {
				if (buffers.Count>0) {
					int idx = (int)(buffers.Count/2);
					return (Buffer) buffers[idx];
				} else
					return null;
			}
		}

		public Buffer LastBuffer {		
			get {
				if (buffers.Count>0) 
					return (Buffer) buffers[buffers.Count-1];
				else
					return null;
			}
		}

		public int Position(Buffer buf) {
			return buffers.IndexOf(buf);
		}

		public Buffer NextBuffer(Buffer prev) {
			lock(buffers.SyncRoot) {
				if (prev==null) return LastBuffer;
				if (prev.Invalid) return LastBuffer;
				int idx=buffers.IndexOf(prev);
				if (idx==0) return null;
				if (idx<0) throw new BufferUnderrunException();
				return (Buffer) buffers[idx-1];	
			}
		}

		public Buffer NextBuffer(Buffer prev, TimeSpan maxWait) {
			lock(buffers.SyncRoot) {
				DateTime start = DateTime.Now;
				while(true) {
					if (buffers.Count>0) {
						Buffer next = NextBuffer(prev);
						if (next!=null) return next;
					}
					if (DateTime.Now - start > maxWait) {
						Log.Error("Reset buffer position max wait exceeded");
						return MiddleBuffer;
					}
					System.Threading.Monitor.Wait(buffers.SyncRoot,TimeSpan.FromSeconds(2),true);
				}
			}
		}

		public BufferIterator GetBufferIterator() {
			return new BufferIterator(this);
		}
	}

	public class BufferIterator {

		Buffer curBuf;
		BufferManager bm;
		int underruns;

		protected internal BufferIterator(BufferManager bm) {
			lock(this) {
				this.bm = bm;
				curBuf = null;
			}
		}

		public int Position {
			get { 
				if (curBuf==null) return -1;
				return curBuf.Position;
			}
		}

		public Buffer NextBuffer() {
			try {
				curBuf = bm.NextBuffer(curBuf,TimeSpan.FromSeconds(30));
			} catch (BufferUnderrunException ex) {
				Log.Warn("Buffer underrun");
				underruns++;
				curBuf = bm.MiddleBuffer;
			}
			return curBuf;
		}
	}

	public class BufferUnderrunException:ApplicationException {
		public BufferUnderrunException():base("Buffer Underrun") {}
	}
}